/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2022-2022. All rights reserved.
 */

package com.huawei.saashousekeeper.config;

import com.huawei.saashousekeeper.config.binding.DataSourceBindingStrategy;
import com.huawei.saashousekeeper.config.binding.DefaultDataSourceBindingStrategy;
import com.huawei.saashousekeeper.config.binding.DefaultSchemaBindingStrategy;
import com.huawei.saashousekeeper.config.binding.SchemaBindingStrategy;
import com.huawei.saashousekeeper.config.dynamicdatasource.DataSourceRegistry;
import com.huawei.saashousekeeper.config.dynamicdatasource.DynamicRoutingDataSource;
import com.huawei.saashousekeeper.constants.Constants;
import com.huawei.saashousekeeper.context.TenantContext;
import com.huawei.saashousekeeper.dbpool.creator.DataSourceCreator;
import com.huawei.saashousekeeper.dbpool.creator.DruidDataSourceCreator;
import com.huawei.saashousekeeper.dbpool.creator.HikariDataSourceCreator;
import com.huawei.saashousekeeper.dbpool.druid.DruidDynamicDataSourceConfiguration;
import com.huawei.saashousekeeper.interceptor.*;
import com.huawei.saashousekeeper.properties.DynamicSourceProperties;
import com.huawei.saashousekeeper.properties.ResourcesExcluderProperties;
import com.huawei.saashousekeeper.properties.TenantProperties;
import lombok.extern.log4j.Log4j2;
import org.springframework.amqp.AmqpException;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessagePostProcessor;
import org.springframework.amqp.rabbit.config.DirectRabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.amqp.DirectRabbitListenerContainerFactoryConfigurer;
import org.springframework.boot.autoconfigure.amqp.SimpleRabbitListenerContainerFactoryConfigurer;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Primary;
import org.springframework.integration.handler.MessageProcessor;

import javax.sql.DataSource;
import java.util.List;
import java.util.Optional;

/**
 * 自动装配
 *
 * @since 2022-02-14
 */
@Configuration
@EnableConfigurationProperties(
        value = {TenantProperties.class, ResourcesExcluderProperties.class, DynamicSourceProperties.class})
@Log4j2
@AutoConfigureBefore(value = DataSourceAutoConfiguration.class,
        name = "com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure")
@Import(value = {DruidDynamicDataSourceConfiguration.class})
@ConditionalOnProperty(prefix = "spring.datasource.dynamic", name = "enable", havingValue = "true")
public class TenantAutoConfiguration {
    /**
     * mybatis schema选择拦截器
     *
     * @return mybatis拦截器
     */
    @Bean
    public MybatisInterceptor mybatisInterceptor() {
        return new MybatisInterceptor();
    }

    /**
     * mybatis读写分离拦截器
     *
     * @return mybatis拦截器
     */
    @Bean
    public MybatisReadWriteSeparationInterceptor mybatisReadWriteSeparationInterceptor() {
        return new MybatisReadWriteSeparationInterceptor();
    }

    /**
     * feign调用传递租户标识
     *
     * @return 租户标识传递
     */
    @Bean
    public FeignRequestInterceptor feignRequestInterceptor() {
        return new FeignRequestInterceptor();
    }

    /**
     * MQ消息接收时保存租户标识
     *
     * @return AOP切面
     */
    @Bean
    public RabbitMqAspect rabbitMqAspect() {
        return new RabbitMqAspect();
    }

    /**
     * 数据源切面
     *
     * @return 数据源切面
     */
    @Bean
    public DataResourceAspect dataResourceAspect() {
        return new DataResourceAspect();
    }

    /**
     * MQ消息发送时拦截
     *
     * @param factory CachingConnectionFactory
     * @return RabbitTemplate
     */
    @Bean("tenantRouteRabbitTemplate")
    @ConditionalOnProperty(prefix = "spring.schema", name = "rabbitmq-route-enabled", havingValue = "true")
    public RabbitTemplate rabbitTemplate(CachingConnectionFactory factory) {
        RabbitTemplate rabbitTemplate = new RabbitTemplate(factory);

        // 消息发送前保存租户标识
        rabbitTemplate.setBeforePublishPostProcessors(new MessageProcessor());
        return rabbitTemplate;
    }

    /**
     * 消息接收后拦截
     *
     * @param configurer        配置
     * @param connectionFactory 连接工厂
     * @return SimpleRabbitListenerContainerFactory
     */
    @Bean("rabbitListenerContainerFactory")

    // 默认的监听模式为simple
    @ConditionalOnProperty(prefix = "spring.rabbitmq.listener", name = "type", havingValue = "simple",
            matchIfMissing = true)
    @ConditionalOnBean(name = "tenantRouteRabbitTemplate")
    public SimpleRabbitListenerContainerFactory simpleRabbitListenerContainerFactory(
            SimpleRabbitListenerContainerFactoryConfigurer configurer, ConnectionFactory connectionFactory) {
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        factory.setAfterReceivePostProcessors(message -> {
            // 消息不为空就去获取租户标识
            Optional.ofNullable(message).ifPresent(msg -> {
                String tenantDomain = msg.getMessageProperties().getHeader(Constants.TENANT_DOMAIN);

                Optional.ofNullable(tenantDomain).orElseThrow(RuntimeException::new);
                TenantContext.setDomain(tenantDomain, true);
            });
            return message;
        });
        configurer.configure(factory, connectionFactory);
        return factory;
    }

    /**
     * 直连场景
     *
     * @param configurer        配置
     * @param connectionFactory 连接工厂
     * @return DirectRabbitListenerContainerFactory
     */
    @Bean("rabbitListenerContainerFactory")

    // 如果是direct模式则装配这个bean
    @ConditionalOnProperty(prefix = "spring.rabbitmq.listener", name = "type", havingValue = "direct")
    @ConditionalOnBean(name = "tenantRouteRabbitTemplate")
    public DirectRabbitListenerContainerFactory directRabbitListenerContainerFactory(
            DirectRabbitListenerContainerFactoryConfigurer configurer, ConnectionFactory connectionFactory) {
        DirectRabbitListenerContainerFactory factory = new DirectRabbitListenerContainerFactory();
        factory.setAfterReceivePostProcessors(message -> {
            // 消息不为空就去获取租户标识
            Optional.ofNullable(message).ifPresent(msg -> {
                String tenantDomain = msg.getMessageProperties().getHeader(Constants.TENANT_DOMAIN);

                Optional.ofNullable(tenantDomain).orElseThrow(RuntimeException::new);
                TenantContext.setDomain(tenantDomain, true);
            });
            return message;
        });
        configurer.configure(factory, connectionFactory);
        return factory;
    }

    /**
     * 支持多数据源，多schema配置
     *
     * @param property           配置属性
     * @param dataSourceCreators 数据源创建器
     * @return 动态数据源
     */
    @Bean
    @Primary
    public DataSource dynamicDataSource(DynamicSourceProperties property, List<DataSourceCreator> dataSourceCreators) {
        // 传入全局配置
        DataSourceRegistry registry = new DynamicRoutingDataSource(dataSourceCreators, property);
        return (DataSource) registry;
    }

    /**
     * 数据源创建器-druid
     *
     * @return druid数据源创建器
     */
    @Bean
    public DataSourceCreator druidDataSourceCreator() {
        return new DruidDataSourceCreator();
    }

    /**
     * 数据源创建器-hikari
     *
     * @return hikari数据源创建器
     */
    @Bean
    public DataSourceCreator hikariDataSourceCreator() {
        return new HikariDataSourceCreator();
    }

    /**
     * 默认的schema转换逻辑，需要进行schema路由时使用
     *
     * @return 默认schema适配器
     */
    @Bean
    @ConditionalOnMissingBean(SchemaBindingStrategy.class)
    public SchemaBindingStrategy defaultSchemaStrategy() {
        return new DefaultSchemaBindingStrategy();
    }

    /**
     * 默认的数据源适配逻辑，未自定义扩展时使用
     *
     * @return 数据源适配器
     */
    @Bean
    @ConditionalOnMissingBean(DataSourceBindingStrategy.class)
    public DataSourceBindingStrategy defaultDataSourceStrategy() {
        return new DefaultDataSourceBindingStrategy();
    }

    /**
     * 消息发送
     *
     * @author lWX1156935
     * @since 2022-05-30
     */
    static class MessageProcessor implements MessagePostProcessor {
        @Override
        public Message postProcessMessage(Message message) throws AmqpException {
            String tenantDomain = TenantContext.getDomain();
            log.info("{} send message with rabbit", tenantDomain);

            // 消息发送前保存租户标识
            Optional.ofNullable(tenantDomain)
                    .ifPresent(domain -> message.getMessageProperties().setHeader(Constants.TENANT_DOMAIN, tenantDomain));
            return message;
        }
    }
}
